Skip to content

feat: tag events handlers partial recovery#792

Merged
aintnostressin merged 7 commits intomainfrom
feat/tag-handler-fix-state
Apr 21, 2026
Merged

feat: tag events handlers partial recovery#792
aintnostressin merged 7 commits intomainfrom
feat/tag-handler-fix-state

Conversation

@aintnostressin
Copy link
Copy Markdown
Contributor

Prepares tag handlers for partial recovery.

  • DEL handler: graph-last ordering — Reads tag target from graph first (new get_tag_target query), performs Redis index cleanup, then deletes the graph edge last. This ensures the TAGGED edge survives for retry if Redis ops fail mid-way.
  • DEL handler: guarded decrements — Uses check_set_member to verify the tagger is still in the Redis set before decrementing counters/sending notifications, preventing double-decrement on retry.
  • PUT handler: retry recovery — When put_to_graph returns Updated (edge already exists from a prior attempt), re-runs idempotent Redis index writes to recover from partial failures where graph succeeded but Redis didn't.
  • Idempotent no-op on replay — Tag DEL no longer returns SkipIndexing when the graph edge is already gone; it returns Ok(()) silently.

@aintnostressin aintnostressin requested review from ok300 and tipogi April 3, 2026 16:25
@aintnostressin aintnostressin changed the title feat: tag handlers partial recovery feat: tag events handlers partial recovery Apr 3, 2026
Comment thread nexus-watcher/src/events/handlers/tag.rs Outdated
Comment thread nexus-watcher/src/events/handlers/tag.rs Outdated
Comment thread nexus-common/src/db/graph/queries/get.rs
Comment thread nexus-common/src/models/tag/traits/collection.rs
@aintnostressin aintnostressin requested a review from ok300 April 7, 2026 07:23
@tipogi tipogi added the 👀 watcher Nexus indexer related operations label Apr 8, 2026
@tipogi tipogi added this to the 2026-Q1 milestone Apr 8, 2026
@tipogi tipogi added the 🪡 chores Maintenance and housekeeping label Apr 8, 2026
Comment thread nexus-watcher/src/events/handlers/tag.rs Outdated
Comment thread nexus-watcher/src/events/handlers/tag.rs
@aintnostressin
Copy link
Copy Markdown
Contributor Author

@greptileai review

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 8, 2026

Greptile Summary

This PR refactors the tag event handlers to support partial recovery by adopting a graph-last deletion order, a check_set_member guard to prevent double-decrements on retry, and an Updated-branch retry path for PUT that re-runs idempotent Redis writes.

  • P1: The Updated retry branch in put_sync_post (and put_sync_user) re-runs only the three idempotent Redis writes; all counter increments (UserCounts, PostCounts) and score updates (TagPost, PostsByTagSearch, PostStream) are silently skipped. If the original PUT's graph write succeeded but every Redis op failed, the retry leaves counts and scores permanently incorrect with no auto-healing path.
  • P2: TagUser::del_from_graph is called as the graph-deletion step for both user-tag and post-tag paths, which is functionally correct but reads as a type mismatch for the post-tag case; a clarifying comment is needed.

Confidence Score: 3/5

Safe to merge for the DEL-side improvements; the PUT Updated retry path leaves a known correctness gap in counter/score recovery that should be addressed before this is relied upon in production.

DEL handler changes are well-reasoned and tested. The PUT Updated branch is intentionally partial but could silently leave Redis counts wrong in a graph-wrote/Redis-never-ran failure scenario, which may be hard to detect without monitoring.

nexus-watcher/src/events/handlers/tag.rs — Updated branches in put_sync_post and put_sync_user

Vulnerabilities

No security concerns identified. Tag IDs and user IDs flow through parameterized Cypher queries; no raw string interpolation into queries. The new get_tag_target read-before-delete pattern does not introduce any TOCTOU risk beyond what already exists in the distributed system.

Important Files Changed

Filename Overview
nexus-watcher/src/events/handlers/tag.rs Core handler refactored for partial recovery: DEL now reads graph first and guards decrements via check_set_member; PUT retries idempotent Redis ops on Updated. Missing non-idempotent counter/score recovery in the Updated branch is a correctness concern.
nexus-common/src/models/tag/traits/collection.rs Trait extended with get_target_from_graph and simplified del_from_graph (now uses exec_single_row for fire-and-forget delete). Logic is clean and idempotent-by-default.
nexus-common/src/db/graph/queries/get.rs New get_tag_target query correctly separates the read from the delete to enable graph-last ordering. Cypher logic mirrors the removed portion of delete_tag.
nexus-common/src/db/graph/queries/del.rs delete_tag simplified to a fire-and-forget DELETE with no RETURN clause; consistent with the new exec_single_row call site.
nexus-watcher/tests/event_processor/tags/del_idempotent.rs Two new tests covering retry and replay paths; retry test verifies no double-decrement, replay test verifies idempotent Ok but lacks counter stability assertions.
nexus-watcher/tests/event_processor/tags/retry_post_tag.rs Removed stale assertions that expected a SkipIndexing retry event after del; consistent with the new idempotent no-op behavior.
nexus-watcher/tests/event_processor/tags/retry_user_tag.rs Same cleanup as retry_post_tag.rs — SkipIndexing assertions removed, aligning with idempotent no-op del behavior.
nexus-watcher/tests/event_processor/tags/mod.rs Registers the new del_idempotent test module. No issues.

Sequence Diagram

sequenceDiagram
    participant H as tag::del handler
    participant G as Neo4j Graph
    participant R as Redis

    Note over H: DEL flow (graph-last)
    H->>G: get_tag_target(user_id, tag_id)
    G-->>H: Some(tagged_id, post_id, author_id, label) OR None

    alt Edge found
        H->>R: check_set_member(tagger_in_index?)
        R-->>H: bool
        alt tagger_in_index == true
            H->>R: decrement counters + scores
            H->>R: send untag notification
        end
        H->>R: SREM tagger (idempotent)
        H->>R: del_from_index / PostsByTagSearch cleanup (idempotent)
        H->>G: del_from_graph (graph-last)
    else Edge already gone (idempotent replay)
        H-->>H: return Ok(()) silently
    end

    Note over H: PUT flow (Updated = prior attempt)
    H->>G: put_to_graph(...)
    G-->>H: Updated (edge existed)
    H->>R: add_tagger_to_index (idempotent set-add)
    H->>R: PostsByTagSearch::put_to_index (idempotent)
    H->>R: TagSearch::put_to_index (idempotent)
    Note over H,R: CounterIncrements / ScoreUpdates NOT retried
Loading

Reviews (1): Last reviewed commit: "review" | Re-trigger Greptile

Comment thread nexus-watcher/src/events/handlers/tag.rs
Comment thread nexus-watcher/src/events/handlers/tag.rs Outdated
Comment thread nexus-watcher/tests/event_processor/tags/del_idempotent.rs
Copy link
Copy Markdown
Contributor

@ok300 ok300 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few NITs, but otherwise LGTM.

Comment thread nexus-watcher/src/events/handlers/tag.rs Outdated
Comment thread nexus-watcher/tests/event_processor/tags/del_idempotent.rs Outdated
Comment thread nexus-watcher/tests/event_processor/tags/del_idempotent.rs Outdated
@aintnostressin aintnostressin force-pushed the feat/tag-handler-fix-state branch from 8c553b5 to 13f24c9 Compare April 20, 2026 14:44
@aintnostressin aintnostressin force-pushed the feat/tag-handler-fix-state branch from 13f24c9 to fbab9b4 Compare April 21, 2026 06:55

/// Reads tag target details from the graph without deleting the TAGGED edge.
/// Same return shape as `del_from_graph`.
async fn get_target_from_graph(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused dead code? get_target_from_graph was added, but is never used.

@aintnostressin aintnostressin merged commit b28a6be into main Apr 21, 2026
3 checks passed
@aintnostressin aintnostressin deleted the feat/tag-handler-fix-state branch April 21, 2026 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🪡 chores Maintenance and housekeeping 👀 watcher Nexus indexer related operations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants